﻿using System;
using System.Net;
using System.Net.NetworkInformation;
using System.Net.Sockets;
using System.Threading;
using Netbits.Common.Threading;

namespace Netbits.Common.Network
{
	public class Multicast : IDisposable
	{
		#region Private Variables

		SaveEventHandler<Multicast, byte[], int> _EventReceive;

		Socket _SocketReceive;
		Socket _SocketSend;

		int _InitDone;
		int _UseReceive = 1;
		int _UseSend = 1;

		IPAddress _Address = IPAddress.Any;
		BindEndPoint _BindTo;
		int _TTL = 10;
		int _MTU = 1500;
		int _UseLoop = 1;
		byte[] _ReceiveBuffer;

		Lock _Lock;

		#endregion

		#region Constructor

		public Multicast()
		{
			_Lock = new Lock( true );
			_EventReceive = new SaveEventHandler<Multicast, byte[], int>();
		}

		#endregion

		#region Public Properties

		/// <summary>
		/// 
		/// </summary>
		/// <value>true | false</value>
		public bool UseReceive
		{
			get
			{
				return (_UseReceive == 1);
			}
			set
			{
				Interlocked.Exchange( ref _UseReceive, value ? 1 : 0 );
			}
		}

		/// <summary>
		/// 
		/// </summary>
		/// <value>true | false</value>
		public bool UseSend
		{
			get
			{
				return (_UseSend == 1);
			}
			set
			{
				Interlocked.Exchange( ref  _UseSend, value ? 1 : 0 );
			}
		}

		/// <summary>
		/// 
		/// </summary>
		/// <value>true | false</value>
		public bool InitDone
		{
			get
			{
				return (_InitDone == 1);
			}
		}

		/// <summary>
		/// 
		/// </summary>
		public int TTL
		{
			get
			{
				return _TTL;
			}
			set
			{
				Interlocked.Exchange( ref _TTL, value );
			}
		}

		/// <summary>
		/// 
		/// </summary>
		public int MTU
		{
			get
			{
				return _MTU;
			}
			set
			{
				Interlocked.Exchange( ref _MTU, value );
			}
		}

		/// <summary>
		/// 
		/// </summary>
		public BindEndPoint BindAddress
		{
			get
			{
				return _BindTo;
			}
			set
			{
				Interlocked.Exchange( ref _BindTo, value );
			}
		}

		/// <summary>
		/// 
		/// </summary>
		public IPAddress Address
		{
			get
			{
				return _Address;
			}
			set
			{
				Interlocked.Exchange( ref _Address, value );
			}
		}

		/// <summary>
		///
		/// </summary>
		public bool Loop
		{
			get
			{
				return (_UseLoop == 1);
			}
			set
			{
				Interlocked.Exchange( ref _UseLoop, value ? 1 : 0 );
			}
		}

		#endregion

		#region Public Events

		public SaveEventHandler<Multicast, byte[], int> Receive
		{
			get
			{
				return _EventReceive;
			}
		}

		#endregion

		#region Public Functions

		public void Init()
		{
			if( InitDone )
				return;

			using( _Lock.Write() )
			{
				bool foundInterface = false;
				if( _BindTo.Get(0).Address.ToString() != IPAddress.Any.ToString() )
				{
					foreach( NetworkInterface ni in NetworkInterface.GetAllNetworkInterfaces() )
					{
						foreach( UnicastIPAddressInformation uiai in ni.GetIPProperties().UnicastAddresses )
						{
							if( string.Compare( uiai.Address.ToString(), _BindTo.Get(0).Address.ToString(), true ) != 0 )
								continue;

							foundInterface = true;
						}

						if( foundInterface )
						{
							_MTU = ni.GetIPProperties().GetIPv4Properties().Mtu;
							if( _MTU <= 0 )
								_MTU = 1500;
							break;
						}
					}
				}

				IPEndPoint ip = _BindTo.Get( 0 );

				if( _UseReceive == 1 )
				{
					_SocketReceive = new Socket( AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp );
					_SocketReceive.SetSocketOption( SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, 1 );
					_SocketReceive.Bind( ip );
					_ReceiveBuffer = new byte[_MTU];

					_SocketReceive.SetSocketOption( SocketOptionLevel.IP, SocketOptionName.AddMembership, new MulticastOption( _Address, ip.Address ) );
					_StartReceive();
				}

				if( _UseSend == 1 )
				{
					_SocketSend = new Socket( AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp );
					_SocketSend.SetSocketOption( SocketOptionLevel.IP, SocketOptionName.MulticastTimeToLive, _TTL );
					_SocketSend.SetSocketOption( SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, 1 );
					_SocketSend.SetSocketOption( SocketOptionLevel.IP, SocketOptionName.MulticastLoopback, _UseLoop );
					_SocketSend.Connect( ip );

					_SocketSend.SetSocketOption( SocketOptionLevel.IP, SocketOptionName.AddMembership, new MulticastOption( _Address, ip.Address ) );
				}

				Interlocked.Exchange( ref _InitDone, 1 );
			}
		}

		public void Deinit()
		{
			using( _Lock.Write() )
			{
				if( _InitDone == 0 )
					return;

				if( _SocketReceive != null )
				{
					_SocketReceive.Close();
					_SocketReceive = null;
				}

				if( _SocketSend != null )
				{
					_SocketSend.Close();
					_SocketSend = null;
				}

				Interlocked.Exchange( ref _InitDone, 0 );
			}
		}

		public int Send( byte[] Buffer_ )
		{
			return Send( Buffer_, Buffer_.Length );
		}

		public int Send( byte[] Buffer_, int Size_ )
		{
			using( _Lock.Write() )
			{
				if( _InitDone == 0 || _SocketSend == null )
					return -1;

				return _SocketSend.Send( Buffer_, Size_, SocketFlags.None );
			}
		}

		#endregion

		#region Private Functions

		void _StartReceive()
		{
			try
			{
				_SocketReceive.BeginReceive( _ReceiveBuffer, 0, _MTU, SocketFlags.None, new AsyncCallback( _StartReceiveCallback ), null );
			}
			catch
			{
			}
		}

		void _StartReceiveCallback( IAsyncResult Result_ )
		{
			try
			{
				int bytes = _SocketReceive.EndReceive( Result_ );
				_EventReceive.Notify( this, _ReceiveBuffer, bytes );
				_StartReceive();
			}
			catch( SocketException )
			{
				Deinit();
			}
			catch( ObjectDisposedException )
			{
			}
			catch
			{
			}
		}

		#endregion
	}
}
